/***************************************************************************
 *   Copyright (C) 2009 - 2010 by Simon Qian <SimonQian@SimonQian.com>     *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/
#include "vsf.h"

#ifdef VSFUSBH_BTHCI_CFG_SCOEN
#define VSFUSBH_BTHCI_CFG_SCOEN			0
#endif

#define BTHCI_PACKET_TYPE_CMD			1
#define BTHCI_PACKET_TYPE_ACL			2
#define BTHCI_PACKET_TYPE_SCO			3
#define BTHCI_PACKET_TYPE_EVENT			4
#define BTHCI_PACKET_TYPE_OUT			0x80

#define BTHCI_BUFSIZE					(255 + 3)

struct vsfusbh_bthci_cb_t
{
	void (*on_new)(void *dev, struct vsfusbh_device_id_t *id);
	void (*on_del)(void *dev);
	void (*on_packet)(void *dev, uint8_t type, uint8_t *packet, uint16_t size);
} static bthci_cb;

// input/output control block
struct vsfusbh_bthci_t;
struct vsfusbh_bthci_iocb_t
{
	struct vsfhcd_urb_t *urb;
	struct vsfsm_t sm;
	struct vsfusbh_bthci_t *bthci;
	uint8_t type;
	uint8_t ep0claimed : 1;
};

struct vsfusbh_bthci_t
{
	struct vsfusbh_t *usbh;
	struct vsfusbh_device_t *dev;
	struct vsfusbh_ifs_t *ifs;
	struct vsfusbh_device_id_t id;

	uint8_t event_ep;
	uint8_t event_interval;
	uint8_t aclout_ep;
	uint8_t aclin_ep;
#if VSFUSBH_BTHCI_CFG_SCOEN
	uint8_t scoout_ep;
	uint8_t scoin_ep;
#endif

	struct vsfsm_pt_t init_pt;

	union
	{
		struct
		{
			struct vsfusbh_bthci_iocb_t event_icb;
			struct vsfusbh_bthci_iocb_t aclin_icb;
#if VSFUSBH_BTHCI_CFG_SCOEN
			struct vsfusbh_bthci_iocb_t scoin_icb;
#endif
			union
			{
				struct
				{
					struct vsfusbh_bthci_iocb_t cmd_ocb;
					struct vsfusbh_bthci_iocb_t aclout_ocb;
#if VSFUSBH_BTHCI_CFG_SCOEN
					struct vsfusbh_bthci_iocb_t scoout_ocb;
#endif
				};
#if VSFUSBH_BTHCI_CFG_SCOEN
				struct vsfusbh_bthci_iocb_t ocb[3];
#else
				struct vsfusbh_bthci_iocb_t ocb[2];
#endif
			};
		};
		struct
		{
#if VSFUSBH_BTHCI_CFG_SCOEN
			struct vsfusbh_bthci_iocb_t iocb[6];
#else
			struct vsfusbh_bthci_iocb_t iocb[4];
#endif
		};
	};
};

static vsf_err_t vsfusbh_bthci_init_thread(struct vsfsm_pt_t *pt, vsfsm_evt_t evt)
{
	struct vsfusbh_bthci_t *bthci = (struct vsfusbh_bthci_t *)pt->user_data;
	struct vsfhcd_urb_t *urb = bthci->event_icb.urb;
	vsf_err_t err;

	vsfsm_pt_begin(pt);

	if (vsfsm_crit_enter(&bthci->dev->ep0_crit, pt->sm))
		vsfsm_pt_wfe(pt, VSFSM_EVT_EP0_CRIT);

	urb->transfer_buffer = NULL;
	urb->transfer_length = 0;
	err = vsfusbh_set_interface(bthci->usbh, urb, 1, 0);
	if (err != VSFERR_NONE)
	{
		vsfsm_crit_leave(&bthci->dev->ep0_crit);
		return err;
	}
	vsfsm_pt_wfe(pt, VSFSM_EVT_URB_COMPLETE);
	vsfsm_crit_leave(&bthci->dev->ep0_crit);
	if (urb->status != URB_OK)
		return VSFERR_FAIL;

	vsfsm_pt_end(pt);

	return VSFERR_NONE;
}

static struct vsfsm_state_t *
vsfusbh_bthci_evt_handler_input(struct vsfsm_t *sm, vsfsm_evt_t evt);
static struct vsfsm_state_t *
vsfusbh_bthci_evt_handler_init(struct vsfsm_t *sm, vsfsm_evt_t evt)
{
	vsf_err_t err;
	struct vsfusbh_bthci_t *bthci = (struct vsfusbh_bthci_t *)sm->user_data;

	switch (evt)
	{
	case VSFSM_EVT_INIT:
	case VSFSM_EVT_URB_COMPLETE:
	case VSFSM_EVT_DELAY_DONE:
		err = vsfusbh_bthci_init_thread(&bthci->init_pt, evt);
		if (err <= 0)
		{
			// finialize event_icb.sm(used in initialization) first
			vsfsm_fini(&bthci->event_icb.sm);
			if (err < 0)
			{
				vsfusbh_remove_interface(bthci->usbh, bthci->dev, bthci->ifs);
			}
			else if ((err == VSFERR_NONE) && (bthci_cb.on_new != NULL))
			{
				struct vsfhcd_urb_t *urb = bthci->event_icb.urb;
				if (NULL == vsfusbh_alloc_urb_buffer(urb, BTHCI_BUFSIZE))
				{
					vsfusbh_remove_interface(bthci->usbh, bthci->dev, bthci->ifs);
					break;
				}
				urb->pipe = usb_rcvintpipe(urb->hcddev, bthci->event_ep);
				urb->interval = bthci->event_interval;
				urb->transfer_length = BTHCI_BUFSIZE;
				bthci->event_icb.sm.init_state.evt_handler = vsfusbh_bthci_evt_handler_input;
				bthci->event_icb.sm.user_data = (void*)&bthci->event_icb;

				vsfsm_init(&bthci->event_icb.sm);
				vsfsm_init(&bthci->aclin_icb.sm);
#if VSFUSBH_BTHCI_CFG_SCOEN
				vsfsm_init(&bthci->scoin_icb.sm);
#endif
				bthci_cb.on_new(bthci, &bthci->id);
			}
		}
		break;
	default:
		break;
	}
	return NULL;
}

static struct vsfsm_state_t *
vsfusbh_bthci_evt_handler_out(struct vsfsm_t *sm, vsfsm_evt_t evt)
{
	struct vsfusbh_bthci_iocb_t *ocb = (struct vsfusbh_bthci_iocb_t *)sm->user_data;

	switch (evt)
	{
	case VSFSM_EVT_URB_COMPLETE:
		ocb->sm.init_state.evt_handler = NULL;
		if (ocb->ep0claimed)
		{
			ocb->ep0claimed = false;
			vsfsm_crit_leave(&ocb->bthci->dev->ep0_crit);
		}
		if ((URB_OK == ocb->urb->status) && (bthci_cb.on_packet != NULL))
		{
			bthci_cb.on_packet(ocb->bthci, ocb->type | BTHCI_PACKET_TYPE_OUT, NULL, 0);
		}
		vsfsm_fini(&ocb->sm);
		break;
	}
	return NULL;
}

static struct vsfsm_state_t *
vsfusbh_bthci_evt_handler_input(struct vsfsm_t *sm, vsfsm_evt_t evt)
{
	vsf_err_t err;
	struct vsfusbh_bthci_iocb_t *icb = (struct vsfusbh_bthci_iocb_t *)sm->user_data;
	struct vsfusbh_bthci_t *bthci = icb->bthci;
	struct vsfusbh_t *usbh = bthci->usbh;
	struct vsfhcd_urb_t *urb = icb->urb;

	switch (evt)
	{
	case VSFSM_EVT_INIT:
		err = vsfusbh_submit_urb(usbh, urb);
		if (err != VSFERR_NONE)
		{
			// TODO
		}
		break;
	case VSFSM_EVT_URB_COMPLETE:
		if (urb->status == URB_OK)
		{
			if(bthci_cb.on_packet != NULL)
				bthci_cb.on_packet(bthci, icb->type, urb->transfer_buffer, urb->actual_length);
		}
		else
		{
			// TODO
		}

		err = icb->type == BTHCI_PACKET_TYPE_EVENT ?
			vsfusbh_relink_urb(usbh, urb) : vsfusbh_submit_urb(usbh, urb);
		if (err != VSFERR_NONE)
		{
			// TODO
		}
		break;
	default:
		break;
	}
	return NULL;
}

static const struct vsfusbh_device_id_t vsfusbh_bthci_id_table[] =
{
	{
		.match_flags = USB_DEVICE_ID_MATCH_DEV_CLASS | USB_DEVICE_ID_MATCH_DEV_SUBCLASS | USB_DEVICE_ID_MATCH_DEV_PROTOCOL,
		.bDeviceClass = 0xE0,		// Wireless class: USB_CLASS_WIRELESS_CONTROLLER
		.bDeviceSubClass = 0x01,	// RF subclass
		.bDeviceProtocol = 0x01,	// bt programming protocol
	},
	{0},
};

static void vsfusbh_bthci_freeall(struct vsfusbh_bthci_t *bthci)
{
	struct vsfusbh_t *usbh = bthci->usbh;
	for (int i = 0; i < dimof(bthci->iocb); i++)
		if (bthci->iocb[i].urb != NULL)
			vsfusbh_free_urb(usbh, &bthci->iocb[i].urb);
}

static void *vsfusbh_bthci_probe(struct vsfusbh_t *usbh,
		struct vsfusbh_device_t *dev, struct vsfusbh_ifs_t *ifs,
		const struct vsfusbh_device_id_t *id)
{
	struct vsfusbh_bthci_t *bthci;
	struct usb_interface_desc_t *ifs_desc = ifs->alt[ifs->cur_alt].ifs_desc;
	struct usb_endpoint_desc_t *ep_desc = ifs->alt[ifs->cur_alt].ep_desc;
	struct vsfhcd_urb_t *urb;
	uint8_t epaddr, eptype;

	// only probe interface 0
	if (ifs_desc->bInterfaceNumber != 0)
		return NULL;

	bthci = vsf_bufmgr_malloc(sizeof(struct vsfusbh_bthci_t));
	if (NULL == bthci)
		return NULL;
	memset(bthci, 0, sizeof(struct vsfusbh_bthci_t));

	for (int i = 0; i < dimof(bthci->iocb); i++)
	{
		urb = bthci->iocb[i].urb = vsfusbh_alloc_urb(usbh);
		if (NULL == urb)
			goto free_all;

		// do not allocate transfer_buffer for event_icb.urb(the first one)
		// event_icb.urb is used for initialization at startup
		if (i != 0)
		{
			if (NULL == vsfusbh_alloc_urb_buffer(urb, BTHCI_BUFSIZE))
				goto free_all;
		}

		urb->hcddev = &dev->hcddev;
		urb->timeout = 200;
		urb->notifier_sm = &bthci->iocb[i].sm;
		bthci->iocb[i].sm.user_data = &bthci->iocb[i];
		bthci->iocb[i].bthci = bthci;
	}

	for (int i = 0; i < ifs_desc->bNumEndpoints; i++)
	{
		epaddr = ep_desc->bEndpointAddress;
		eptype = ep_desc->bmAttributes;
		switch (eptype)
		{
#if VSFUSBH_BTHCI_CFG_SCOEN
		case USB_ENDPOINT_XFER_ISOC:
			if (epaddr & USB_ENDPOINT_DIR_MASK)
				bthci->scoin_ep = epaddr & 0x7F;
			else
				bthci->scoout_ep = epaddr & 0x7F;
			break;
#endif
		case USB_ENDPOINT_XFER_BULK:
			if (epaddr & USB_ENDPOINT_DIR_MASK)
				bthci->aclin_ep = epaddr & 0x7F;
			else
				bthci->aclout_ep = epaddr & 0x7F;
			break;
		case USB_ENDPOINT_XFER_INT:
			if (epaddr & USB_ENDPOINT_DIR_MASK)
				bthci->event_ep = epaddr & 0x7F;
			break;
		}
		ep_desc = (struct usb_endpoint_desc_t *)((uint32_t)ep_desc + ep_desc->bLength);
	}
	if (!bthci->event_ep || !bthci->aclin_ep || !bthci->aclout_ep)
		goto free_all;

	bthci->dev = dev;
	bthci->usbh = usbh;
	bthci->ifs = ifs;
	bthci->id = *id;
	bthci->id.idProduct = dev->device_desc->idProduct;
	bthci->id.idVendor = dev->device_desc->idVendor;

	// event_icb is used for init at startup
	bthci->event_icb.type = BTHCI_PACKET_TYPE_EVENT;
	bthci->event_icb.sm.init_state.evt_handler = vsfusbh_bthci_evt_handler_init;
	bthci->event_icb.sm.user_data = bthci;
	bthci->init_pt.user_data = bthci;
	bthci->init_pt.sm = &bthci->event_icb.sm;
	bthci->init_pt.state = 0;

	bthci->aclin_icb.type = BTHCI_PACKET_TYPE_ACL;
	urb = bthci->aclin_icb.urb;
	urb->pipe = usb_rcvbulkpipe(urb->hcddev, bthci->aclin_ep);
	if (!vsfusbh_alloc_urb_buffer(urb, BTHCI_BUFSIZE))
		goto free_all;
	bthci->aclin_icb.sm.init_state.evt_handler = vsfusbh_bthci_evt_handler_input;

#if VSFUSBH_BTHCI_CFG_SCOEN
	bthci->scoin_icb.type = BTHCI_PACKET_TYPE_SCO;
	urb = bthci->scoin_icb.urb;
	urb->pipe = usb_rcvbulkpipe(urb->hcddev, bthci->scoin_ep);
	bthci->scoin_icb.sm.init_state.evt_handler = vsfusbh_bthci_evt_handler_input;
	bthci->scoout_ocb.type = BTHCI_PACKET_TYPE_SCO;
#endif

	bthci->cmd_ocb.type = BTHCI_PACKET_TYPE_CMD;
	bthci->aclout_ocb.type = BTHCI_PACKET_TYPE_ACL;

	vsfsm_init(&bthci->event_icb.sm);
	return bthci;

free_all:
	vsfusbh_bthci_freeall(bthci);
	vsf_bufmgr_free(bthci);
	return NULL;
}

static void vsfusbh_bthci_disconnect(struct vsfusbh_t *usbh,
		struct vsfusbh_device_t *dev, void *priv)
{
	struct vsfusbh_bthci_t *bthci = priv;

	if (bthci_cb.on_del != NULL)
		bthci_cb.on_del(bthci);

	vsfusbh_bthci_freeall(bthci);

	vsfsm_fini(&bthci->event_icb.sm);
	vsfsm_fini(&bthci->aclin_icb.sm);
#if VSFUSBH_BTHCI_CFG_SCOEN
	vsfsm_fini(&bthci->scoin_icb.sm);
#endif
	vsf_bufmgr_free(bthci);
}

const struct vsfusbh_class_drv_t vsfusbh_bthci_drv =
{
	.name = "bthci_usb",
	.id_table = vsfusbh_bthci_id_table,
	.probe = vsfusbh_bthci_probe,
	.disconnect = vsfusbh_bthci_disconnect,
};

vsf_err_t vsfusbh_bthci_config_cb(
		void (*on_new)(void *dev, struct vsfusbh_device_id_t *id),
		void (*on_del)(void *dev),
		void (*on_packet)(void *dev, uint8_t type, uint8_t *packet, uint16_t size))
{
	bthci_cb.on_new = on_new;
	bthci_cb.on_del = on_del;
	bthci_cb.on_packet = on_packet;
	return VSFERR_NONE;
}

static struct vsfusbh_bthci_iocb_t *
vsfusbh_bthci_get_ocb(struct vsfusbh_bthci_t *bthci, uint8_t type)
{
	struct vsfusbh_bthci_iocb_t *ocb = (struct vsfusbh_bthci_iocb_t *)bthci->ocb;
	for (int i = 0; i < dimof(bthci->ocb); i++, ocb++)
		if ((ocb->type == type) && !ocb->sm.init_state.evt_handler)
			return ocb;
	return NULL;
}

bool vsfusbh_bthci_cansend(void *dev, uint8_t type)
{
	struct vsfusbh_bthci_t *bthci = (struct vsfusbh_bthci_t *)dev;
	struct vsfusbh_bthci_iocb_t *ocb = vsfusbh_bthci_get_ocb(bthci, type);
	return (ocb != NULL) && (type == BTHCI_PACKET_TYPE_CMD ? !ocb->ep0claimed : true);
}

vsf_err_t vsfusbh_bthci_send(void *dev, uint8_t type, uint8_t *packet, uint16_t size)
{
	struct vsfusbh_bthci_t *bthci = (struct vsfusbh_bthci_t *)dev;
	struct vsfusbh_bthci_iocb_t *ocb = vsfusbh_bthci_get_ocb(bthci, type);
	struct vsfhcd_urb_t *urb;

	if (!ocb)
		return VSFERR_NOT_AVAILABLE;
	if (size > BTHCI_BUFSIZE)
		return VSFERR_NOT_SUPPORT;

	ocb->type = type;
	urb = ocb->urb;
	memcpy(urb->transfer_buffer, packet, size);
	urb->transfer_length = size;

	switch (type)
	{
	case BTHCI_PACKET_TYPE_CMD:
		if (ocb->ep0claimed || vsfsm_crit_enter(&bthci->dev->ep0_crit, &ocb->sm))
			return VSFERR_FAIL;
		ocb->ep0claimed = true;
		urb->pipe = usb_sndctrlpipe(urb->hcddev, 0);
		urb->setup_packet.bRequestType = USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_DEVICE;
		urb->setup_packet.bRequest = 0;
		urb->setup_packet.wValue = 0;
		urb->setup_packet.wIndex = 0;
		urb->setup_packet.wLength = urb->transfer_length;
		break;
	case BTHCI_PACKET_TYPE_ACL:
		urb->transfer_flags |= URB_ZERO_PACKET;
		urb->pipe = usb_sndbulkpipe(urb->hcddev, bthci->aclout_ep);
		break;
	case BTHCI_PACKET_TYPE_SCO:
#if VSFUSBH_BTHCI_CFG_SCOEN
		urb->pipe = usb_sndisocpipe(urb->hcddev, bthci->scoout_ep);
		break;
#else
		return VSFERR_NOT_SUPPORT;
#endif
	}
	ocb->sm.init_state.evt_handler = vsfusbh_bthci_evt_handler_out;
	vsfsm_init(&ocb->sm);
	return vsfusbh_submit_urb(bthci->usbh, urb);
}

